//////////////////////////////////////
// Fast Container Library           //
// MIT License - Copyright (c) 2012 //
//////////////////////////////////////

#pragma once

#include "common.h"
#include "type_traits.h"
#include "iterator.h"
#include "allocator.h"
#include "dynamic_array.h"


FC_NAMESPACE_BEGIN



template <class T, class Allocator>
class multi_array : public dynamic_array<T, Allocator>
{
public:
	typedef dynamic_array<T, Allocator>					base_type;
	typedef multi_array<T, Allocator>					this_type;
	typedef typename base_type::allocator_type			allocator_type;
	typedef typename base_type::size_type				size_type;
	typedef typename base_type::value_type				value_type;
	typedef typename base_type::reference				reference;
	typedef typename base_type::const_reference			const_reference;
	typedef typename base_type::iterator				iterator;
	typedef typename base_type::const_iterator			const_iterator;

	using base_type::begin;
	using base_type::end;
	using base_type::iterator_is_valid;

	using base_type::m_data;
	using base_type::m_size;
	using base_type::m_allocator;

	multi_array() : base_type()
		{
			m_dimensions[0] = 0;
			m_dimensions[1] = 0;
			m_dimensions[2] = 0;
			m_dimensions[3] = 0;
		}

	multi_array( size_t size ) : base_type()
		{
			resize(0, 0, size);
		}

	multi_array( size_t y, size_t x ) : base_type()
		{
			resize(0, y, x);
		}

	multi_array( size_t z, size_t y, size_t x ) : base_type()
		{
			resize(z, y, x);
		}

	multi_array( const this_type& x ) : base_type()
		{
			set_dimensions(0, 0, 0);
			*this = x;
		}

	const this_type &operator = ( const this_type &x )
	{
		if( this != &x )
		{
			base_type::operator =(x);
			m_dimensions[0] = x.m_dimensions[0];
			m_dimensions[1] = x.m_dimensions[1];
			m_dimensions[2] = x.m_dimensions[2];
			m_dimensions[3] = x.m_dimensions[3];
		}

		return *this;
	}

	reference operator () ( size_t x ) { return m_data[ x ]; }
	reference operator () ( size_t y, size_t x ) { return m_data[ offset(y, x) ]; }
	reference operator () ( size_t z, size_t y, size_t x ) { return m_data[ offset(z, y, x) ]; }

	const_reference operator () ( size_t x ) const { return m_data[ x ]; }
	const_reference operator () ( size_t y, size_t x ) const { return m_data[ offset(y, x) ]; }
	const_reference operator () ( size_t z, size_t y, size_t x ) const { return m_data[ offset(z, y, x) ]; }

	reference at( size_t x ) { return m_data[ x ]; }
	reference at( size_t y, size_t x ) { return m_data[ offset(y, x) ]; }
	reference at( size_t z, size_t y, size_t x ) { return m_data[ offset(z, y, x) ]; }

	const_reference at( size_t x ) const { return m_data[ x ]; }
	const_reference at( size_t y, size_t x ) const { return m_data[ offset(y, x) ]; }
	const_reference at( size_t z, size_t y, size_t x ) const { return m_data[ offset(z, y, x) ]; }

	iterator get_iter( size_t x ) { return const_iterator(m_data + x); }
	iterator get_iter( size_t y, size_t x ) { return const_iterator(m_data + offset(y, x)); }
	iterator get_iter( size_t z, size_t y, size_t x ) { return const_iterator(m_data + offset(z, y, x)); }

	const_iterator get_iter( size_t x ) const { return const_iterator(m_data + x); }
	const_iterator get_iter( size_t y, size_t x ) const { return const_iterator(m_data + offset(y, x)); }
	const_iterator get_iter( size_t z, size_t y, size_t x ) const { return const_iterator(m_data + offset(z, y, x)); }

	size_t offset( size_t y, size_t x ) const { return (x + y * m_dimensions[0]); }
	size_t offset( size_t z, size_t y, size_t x ) const { return (x + y * m_dimensions[0] + z * m_dimensions[3]); }

	size_t x() const { return m_dimensions[0]; }
	size_t y() const { return m_dimensions[1]; }
	size_t z() const { return m_dimensions[2]; }


	void assign( const T& value )
	{
		assign( begin(), end(), value );
	}

	void assign( iterator first, iterator last, const T& value = T() )
	{
		FC_ASSERT(iterator_is_valid(first));
		FC_ASSERT(iterator_is_valid(last));
		FC_ASSERT(first <= last);
		fc::fill( first, last, value );
	}

	void resize( size_t x ) { resize( 0, 0, x ); }
	void resize( size_t y, size_t x ) { resize( 0, y, x ); }
	void resize( size_t z, size_t y, size_t x )
	{
		const size_t oldSize = m_size;
		const size_t newSize = get_total_size( z, y, x );
		base_type::resize(newSize);

		set_dimensions( z, y, x );
	}

protected:
	void set_dimensions( size_t z, size_t y, size_t x )
	{
		m_dimensions[0] = x; m_dimensions[1] = y; m_dimensions[2] = z;
		m_dimensions[3] = (x * y);

		m_size = get_total_size( z, y, x );
	}

	size_t get_total_size( size_t z, size_t y, size_t x ) const
	{
		if( z > 0 )
			return (x * y * z);
		else if( y > 0 )
			return (x * y);
		return (x);
	}

private:
	size_t m_dimensions[4];
};



template <class T> inline
	bool operator ==( const multi_array<T>& a, const multi_array<T>& b )
	{
		return( &a == &b || (a.size() == b.size() && fc::equal(a.begin(), a.end(), b.begin())) );
	}

template <class T> inline
	bool operator !=( const multi_array<T>& a, const multi_array<T>& b )
	{
		return !(a == b);
	}



FC_NAMESPACE_END
